/***************************************************************************/
/*                                                                         */
/*                            www.xbdev.net                                */
/*                         bkenwright@xbdev.net                            */
/*                                3ds.cpp                                  */
/*                                                                         */
/***************************************************************************/

#include "3ds.h"

C3DS::C3DS(void)
{
	m_iNumMeshs     = 0;
	m_iNumMaterials = 0;
}

C3DS::~C3DS(void){}


/***************************************************************************/
/*                                                                         */
/* Some user functions to make the reading of the .3ds file easier         */
/*                                                                         */
/***************************************************************************/
/* Helper Functions that make our parsing of the chunks easier             */
/***************************************************************************/


void C3DS::ReadChunk(stChunk *pChunk)
{
	unsigned short ID             = 0;
	unsigned int bytesRead        = 0;
	unsigned int bChunkLength     = 0;

	bytesRead = (unsigned int)fread(&ID, 1, 2, m_fp);
	bytesRead += (unsigned int)fread(&bChunkLength, 1, 4, m_fp);

	pChunk->ID          = ID;
	pChunk->length      = bChunkLength;
	pChunk->bytesRead   = bytesRead;

}

void C3DS::SkipChunk(stChunk *pChunk)
{
	int buffer[50000] = {0};

	fread(buffer, 1, pChunk->length - pChunk->bytesRead, m_fp);
}

/***************************************************************************/
/*                                                                         */
/* Helper Fuction, simply reads in the string from the file pointer, until */
/* a null is reached, then returns how many bytes was read.                */
/*                                                                         */
/***************************************************************************/
int C3DS::GetString(char* pBuffer)
{
	int index = 0;

	char buffer[100] = {0};

	fread(buffer, 1, 1, m_fp);

	while( *(buffer + index++) != 0)
	{
		fread(buffer + index, 1, 1, m_fp);
	}

	strcpy( pBuffer, buffer );

	return (int)(strlen(buffer) + 1);
}

/***************************************************************************/
/*                                                                         */
/* This little function reads the matrial data for our individual object,  */
/* So it determines which face references which material, in our material  */
/* list.                                                                   */
/*                                                                         */
/***************************************************************************/
void C3DS::ReadMeshMaterials(stChunk* Chunk)
{
	// Material Name Where Referencing
	char str[256];
	unsigned int characterlen = GetString(str);
	Chunk->bytesRead += characterlen;

	unsigned short iNumFaces = 0;
	Chunk->bytesRead += (unsigned int)fread(&iNumFaces, 1, 2, m_fp);

	unsigned short *FaceAssignedThisMaterial = new unsigned short[iNumFaces];
	Chunk->bytesRead += (unsigned int)fread(FaceAssignedThisMaterial, 1,
		iNumFaces*sizeof(unsigned short), m_fp);

	// Determine Which Material It Is In Our List
	int MaterialID = 0;
	for( int cc=0; cc<m_iNumMaterials; ++cc)
	{
		if( strcmp( str, m_pMaterials[cc].szName ) == 0 )
			MaterialID = cc;
	}

	stMesh* pMesh = &(m_pMeshs[m_iNumMeshs - 1]);
	for(int i=0; i<iNumFaces; ++i)
	{
		int iIndex = FaceAssignedThisMaterial[i];
		pMesh->pFaces[iIndex].MaterialID = MaterialID;
	}

	return;
}

/***************************************************************************/
/*                                                                         */
/* We get all the faces...e.g. Triangle Index's into our vertex array, so  */
/* we can actually put our shape together.                                 */
/*                                                                         */
/***************************************************************************/
void C3DS::ReadMeshFaces(stChunk* Chunk)
{
	unsigned int iNumberFaces = 0; //= Chunk->length - 6;
	Chunk->bytesRead += (unsigned int)fread(&iNumberFaces, 1, 2, m_fp);    

	// Each face is 3 points A TRIANGLE!..WOW
	struct stXFace{ unsigned short p1, p2, p3, visibityflag; };
	stXFace *pFaces = new stXFace[iNumberFaces];

	Chunk->bytesRead += (unsigned int)fread(pFaces, 1, iNumberFaces*sizeof(stXFace), m_fp);

	stMesh* pMesh = &(m_pMeshs[m_iNumMeshs - 1]);
	pMesh->pFaces = new stFace[iNumberFaces];
	pMesh->iNumFaces = iNumberFaces;

	for( unsigned int i=0; i<iNumberFaces; ++i )
	{
		pMesh->pFaces[i].A = pFaces[i].p1;
		pMesh->pFaces[i].B = pFaces[i].p2;
		pMesh->pFaces[i].C = pFaces[i].p3;
	}

	delete[] pFaces;


	// Our face material information is a sub-chunk.
	ParseChunk(Chunk);
}

/***************************************************************************/
/*                                                                         */
/* You know all those x,y,z things...yup I mean vertices...well this reads */
/* them all in.                                                            */
/*                                                                         */
/***************************************************************************/
void C3DS::ReadMeshVertices(stChunk* Chunk)
{
	unsigned int iNumberVertices = 0;
	Chunk->bytesRead += (unsigned int)fread(&iNumberVertices, 1, 2, m_fp); 

	// Allocate Memory and dump our vertices to the screen.
	stVert *pVerts = new stVert[iNumberVertices];

	Chunk->bytesRead += (unsigned int)fread( (void*)pVerts, 1, iNumberVertices*sizeof(stVert), m_fp);

	stMesh* pMesh = &(m_pMeshs[m_iNumMeshs - 1]);
	pMesh->pVerts = pVerts;
	pMesh->iNumVerts = iNumberVertices;

	SkipChunk(Chunk);
}

/***************************************************************************/
/*                                                                         */
/* Well if we have a texture, e.g. coolimage.bmp, then we need to load in  */
/* our texture coordinates...tu and tv.                                    */
/*                                                                         */
/***************************************************************************/
void C3DS::ReadMeshTexCoords(stChunk* Chunk)
{
	unsigned short iNumberVertices = 0;
	Chunk->bytesRead += (unsigned int)fread(&iNumberVertices, 1, 2, m_fp); 

	// Allocate Memory and dump our texture for each vertice to the screen.
	stTex *pTex = new stTex[iNumberVertices];

	Chunk->bytesRead += (unsigned int)fread( (void*)pTex, 1, iNumberVertices*sizeof(stTex), m_fp);

	stMesh* pMesh = &(m_pMeshs[m_iNumMeshs - 1]);
	pMesh->pTexs = pTex;

	SkipChunk(Chunk);
}

/***************************************************************************/
/*                                                                         */
/* Read in our objects name...as each object in our 3D world has a name,   */
/* for example Box1, HillMesh...whatever you called your object or object's*/
/* in 3d max before you saved it.                                          */
/*                                                                         */
/***************************************************************************/
void C3DS::GetMeshObjectName(stChunk* Chunk)
{
	// The strange thing is, the next few parts of this chunk represent
	// the name of the object.  Then we start chunking again.
	char str[256];
	unsigned int characterlen = GetString(str);
	Chunk->bytesRead += characterlen;

	stMesh* pMesh = &(m_pMeshs[m_iNumMeshs - 1]);
	strcpy( pMesh->szMeshName, str );

	ParseChunk(Chunk);
}

// Read in our texture's file name (e.g. coolpic.jpg)
void C3DS::GetTexFileName(stChunk* Chunk)
{
	char str[256];
	Chunk->bytesRead += GetString(str);

	stMaterial* pMat = &(m_pMaterials[m_iNumMaterials-1]);
	strcpy( pMat->szTextureFile, str );
}

// Read in our diffuse colour (rgb)
void C3DS::GetDiffuseColour(stChunk* Chunk)
{
	struct stRGB{ unsigned char r, g, b; };
	stRGB DiffColour;

	char ChunkHeader[6];
	Chunk->bytesRead += (unsigned int)fread(ChunkHeader, 1, 6, m_fp);

	Chunk->bytesRead += (unsigned int)fread(&DiffColour, 1, 3, m_fp);

	stMaterial* pM = &(m_pMaterials[m_iNumMaterials-1]);
	pM->Colour.r = DiffColour.r;
	pM->Colour.g = DiffColour.g;
	pM->Colour.b = DiffColour.b;

	SkipChunk(Chunk);
}

// Get the materials name, e.g. default-2- etc
void C3DS::GetMaterialName(stChunk* Chunk)
{
	char str[256];
	Chunk->bytesRead += GetString(str);

	stMaterial* pM = &(m_pMaterials[m_iNumMaterials-1]);
	strcpy(pM->szName, str);
}
/***************************************************************************/
/*                                                                         */
/* If theres a nested sub-chunk, and we know its ID, e.g 0xA000 etc, then  */
/* we can simply add its ID to the switch list, and add a calling sub      */
/* functino which will deal with it.  Else just skip over that Chunk...    */
/* and carry on parsing the rest of our file.                              */
/*                                                                         */
/***************************************************************************/

void C3DS::ParseChunk(stChunk* Chunk)
{
	while(Chunk->bytesRead < Chunk->length)
	{
		stChunk tempChunk = {0};
		ReadChunk(&tempChunk);

		switch( tempChunk.ID)
		{
			// HEADER OUR ENTRY POINT
		case EDIT3DS: //0x3D3D
			ParseChunk(&tempChunk);
			break;

			// MATERIALS
		case MATERIAL: //0xAFFF
			stMaterial newMaterial;
			m_pMaterials.push_back(newMaterial);
			++m_iNumMaterials;
			ParseChunk(&tempChunk);
			break;
		case MAT_NAME: //0xA000 - sz for hte material name "e.g. default 2"
			GetMaterialName(&tempChunk);
			break;
		case MAT_DIFFUSE:  // Diffuse Colour  //0xA020
			GetDiffuseColour(&tempChunk);
			break;
		case MAT_TEXMAP:  //0xA200 - if there's a texture wrapped to it where here
			ParseChunk(&tempChunk);
			break;
		case MAT_TEXFLNM: // 0xA300 -  get filename of the material
			GetTexFileName(&tempChunk);
			break;

			// OBJECT - MESH'S
		case NAMED_OBJECT: //0x4000
			{
				stMesh newMesh;
				m_pMeshs.push_back(newMesh);
				++m_iNumMeshs;
				GetMeshObjectName(&tempChunk);
			}
			break;
		case OBJ_MESH:     //0x4100
			ParseChunk(&tempChunk);
			break;
		case MESH_VERTICES: //0x4110
			ReadMeshVertices(&tempChunk);
			break;
		case MESH_FACES: //0x4120
			ReadMeshFaces(&tempChunk);
			break;
		case MESH_TEX_VERT: //0x4140
			ReadMeshTexCoords(&tempChunk);
			break;
		case MESH_MATER: //0x4130
			ReadMeshMaterials(&tempChunk);
			break;

		default:
			SkipChunk(&tempChunk);
		}

		Chunk->bytesRead += tempChunk.length;
	}
}

/***************************************************************************/
/*                                                                         */
/* Read in .3ds file.                                                      */
/*                                                                         */
/***************************************************************************/
bool C3DS::Create(char* szFileName)
{
	m_fp = fopen(szFileName, "rb");

	stChunk Chunk = {0};
	ReadChunk(&Chunk);

	ParseChunk(&Chunk );

	fclose(m_fp);

	return true;
}

void C3DS::Release()
{
	for(int i=0; i<m_iNumMeshs; ++i)
	{
		delete[] m_pMeshs[i].pVerts;
		delete[] m_pMeshs[i].pFaces;
		delete[] m_pMeshs[i].pTexs;
	}
}


// DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG
// DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG
// DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG DEBUG
// Debugging Function to dump raw valeus to a file

/*void debug_op(char *s)
{
	FILE *fp;
	fp = fopen("t.txt", "a+");
	fprintf(fp, "%s", s);
	fclose(fp);
}*/

// Debugging Function - Simply put it dumps our data to a file, just to check
// that it has read it all in correctly.

/*void DisplayRawData(C3DS* pObj)
{
	char buf[500];
	for( int i=0; i<pObj->m_iNumMeshs; ++i )
	{
		C3DS::stMesh* pMesh = &(pObj->m_pMeshs[i]);

		sprintf(buf, "Shape: %s\n", pMesh->szMeshName);
		debug_op(buf);

		sprintf(buf, "iNumFaces: %d\n", pMesh->iNumFaces);
		debug_op(buf);
		for(int cc=0; cc<pMesh->iNumFaces; ++cc)
		{
			sprintf(buf, "\t %d, \t %d \t %d\n",
				pMesh->pFaces[cc].A, pMesh->pFaces[cc].B, pMesh->pFaces[cc].C);
			debug_op(buf);
		}

		sprintf(buf, "iNumVertices: %d\n", pMesh->iNumVerts);
		debug_op(buf);
		for(int cc=0; cc<pMesh->iNumVerts; ++cc)
		{
			sprintf(buf, "\t %.2f, \t %.2f \t %.2f\n",
				pMesh->pVerts[cc].x,pMesh->pVerts[cc].y,pMesh->pVerts[cc].z);
			debug_op(buf);
		}

		if( pMesh->pTexs != NULL )
		{
			sprintf(buf, "iNumTex: %d\n", pMesh->iNumVerts);
			debug_op(buf);
			for(int cc=0; cc<pMesh->iNumVerts; ++cc)
			{
				sprintf(buf, "\t %.2f, \t %.2f\n",
					pMesh->pTexs[cc].tu, pMesh->pTexs[cc].tv );
				debug_op(buf);
			}
		}

		if( pObj->m_iNumMaterials > 0 )
		{
			sprintf(buf, "Material vs Faces: %d\n", pMesh->iNumFaces);
			debug_op(buf);
			for(int cc=0; cc<pMesh->iNumFaces; ++cc)
			{
				sprintf(buf, "\t MaterialID: %d",
					pMesh->pFaces[cc].MaterialID );
				debug_op(buf);

				int ID = pMesh->pFaces[cc].MaterialID;
				sprintf(buf, "\t, Name: %s\n", pObj->m_pMaterials[ID].szName);
				debug_op(buf);
			}
		}
	}
}*/